import { useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
import { CheckOutlined, CloseOutlined, FormOutlined } from "@ant-design/icons";
import { pick, set } from "lodash-es";
import { Spin } from "antd";
import type { BasicColumn } from "../../types/table";
import { useTableContext } from "../../hooks/useTableContext";
import { CellComponent } from "./CellComponent";
import { createPlaceholderMessage } from "./helper";
// import clickOutside from "@/directives/clickOutside";
import { isArray, isBoolean, isFunction, isNumber, isString } from "@/utils/is";
import { treeToList } from "@/utils/helper/treeHelper";
import { nextTick } from "@/utils";
import "../../style/EditableCell.less";

interface EditableCellPropsType {
	value: string | number | boolean | Record<string, any>;
	record?: Object;
	column: BasicColumn;
	index?: number;
}

const EditableCellDefaultProps = {
	value: "",
	column: {}
};

const EditableCell = (props: EditableCellPropsType) => {
	props = Object.assign({}, { ...EditableCellDefaultProps, ...props });

	const table = useTableContext();
	const [isEdit, setIsEdit] = useState(false);
	const elRef = useRef();
	const [ruleOpen, setRuleOpen] = useState(false);
	const [ruleMessage, setRuleMessage] = useState("");
	const [optionsRef, setOptionsRef] = useState([]);
	const [currentValueRef, setCurrentValueRef] = useState<any>(props.value);
	let defaultValueRef = props.value;
	const [spinning, setSpinning] = useState<boolean>(false);

	const prefixCls = "editable-cell";

	const getComponent = useMemo(() => props.column?.editComponent || "Input", [props.column?.editComponent]) as string;
	const getRule = useMemo(() => props.column?.editRule, [props.column?.editRule]);

	const getRuleOpen = useMemo(() => ruleMessage && ruleOpen, [ruleMessage, ruleOpen]);

	const getIsCheckComp = useMemo(() => {
		const component = getComponent as string;
		return ["Checkbox", "Switch"].includes(component);
	}, [getComponent]);
	const getDisable = useMemo(() => {
		const { editDynamicDisabled } = props.column;
		let disabled = false;
		if (isBoolean(editDynamicDisabled)) disabled = editDynamicDisabled;

		if (isFunction(editDynamicDisabled)) {
			const { record } = props;
			disabled = editDynamicDisabled({ record });
		}
		return disabled;
	}, [props.column?.editDynamicDisabled]);

	const getComponentProps = useMemo(() => {
		const isCheckValue = getIsCheckComp;

		const valueField = isCheckValue ? "checked" : "value";
		const val = currentValueRef;

		const value = isCheckValue ? (isNumber(val) || isBoolean(val) ? val : !!val) : val;

		let compProps = props.column?.editComponentProps ?? ({} as any);
		const { record, column, index } = props;

		if (isFunction(compProps)) compProps = compProps({ text: val, record, column, index }) ?? {};

		// 用临时变量存储 onChange方法 用于 handleChange方法 获取，并删除原始onChange, 防止存在两个 onChange
		compProps.onChangeTemp = compProps.onChange;
		delete compProps.onChange;

		const component = getComponent;
		const apiSelectProps: Record<string, any> = {};
		if (component === "ApiSelect") apiSelectProps.cache = true;

		upEditDynamicDisabled(record, column, value);
		return {
			size: "small",
			// getPopupContainer: () => table?.wrapRef ?? document.body,
			placeholder: createPlaceholderMessage(getComponent),
			...apiSelectProps,
			...compProps,
			[valueField]: value,
			disabled: getDisable
		} as any;
	}, [getIsCheckComp, currentValueRef, props, getDisable]);

	function upEditDynamicDisabled(record, column, value) {
		if (!record) return false;
		const { key, dataIndex } = column;
		if (!key && !dataIndex) return;
		const dataKey = (dataIndex || key) as string;
		set(record, dataKey, value);
	}

	const getValues = useMemo(() => {
		const { editValueMap } = props.column;

		const value = currentValueRef;

		if (editValueMap && isFunction(editValueMap)) return editValueMap(value);

		const component = getComponent;
		if (!component.includes("Select") && !component.includes("Radio")) return value;

		const options = getComponentProps?.options ?? (optionsRef || []);
		const option = options.find(item => `${item.value}` === `${value}`);

		return option?.label ?? value;
	}, [props.column?.editValueMap, getComponent, currentValueRef, optionsRef]);
	const getRowEditable = useMemo(() => {
		const { editable } = props.record || {};
		return !!editable;
	}, [props.record?.editable]);

	const getWrapperStyle = useMemo((): CSSProperties => {
		if (getIsCheckComp || getRowEditable) return {};

		return {
			// width: "calc(100% - 48px)"
		};
	}, [getIsCheckComp, getRowEditable]);

	const getWrapperClass = useMemo(() => {
		const { align = "center" } = props.column;
		return `edit-cell-align-${align}`;
	}, [props.column?.align]);

	function handleEdit() {
		if (getRowEditable || props.column?.editRow) return;
		// ruleMessage.value = "";
		setRuleMessage("");
		// isEdit.value = true;
		setIsEdit(true);
		nextTick(() => {
			// elRef.current?.focus?.({ cursor: "end" });
		});
	}

	async function handleChange(e: any) {
		const component = getComponent;
		let value;
		if (!e) value = e;
		else if (component === "Checkbox") value = e.target.checked;
		else if (component === "Switch") value = e;
		else if (e?.target && Reflect.has(e.target, "value")) value = e.target.value;
		else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) value = e;

		const onChange = getComponentProps?.onChangeTemp;
		if (onChange && isFunction(onChange))
			// eslint-disable-next-line prefer-rest-params
			onChange(...arguments);

		setCurrentValueRef(value);
		table.emit?.("editChange", {
			column: props.column,
			value: currentValueRef,
			record: props.record
		});
		handleSubmitRule();
	}

	async function handleSubmitRule() {
		const { column, record } = props;
		const { editRule } = column;
		const currentValue = currentValueRef;

		if (editRule) {
			if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
				// ruleOpen.value = true;
				setRuleOpen(true);
				const component = getComponent;
				// ruleMessage.value = createPlaceholderMessage(component);
				setRuleMessage(createPlaceholderMessage(component));
				return false;
			}
			if (isFunction(editRule)) {
				const res = await editRule(currentValue, record);
				if (res) {
					// ruleMessage.value = res;
					setRuleMessage(res);
					// ruleOpen.value = true;
					setRuleOpen(true);
					return false;
				} else {
					// ruleMessage.value = "";
					setRuleMessage("");
					return true;
				}
			}
		}
		// ruleMessage.value = "";
		setRuleMessage("");
		return true;
	}

	async function handleSubmit(needEmit = true, valid = true) {
		if (valid) {
			const isPass = await handleSubmitRule();
			if (!isPass) return false;
		}

		const { column, index, record } = props;
		if (!record) return false;
		const { key, dataIndex } = column;
		const value = currentValueRef;
		if (!key && !dataIndex) return;

		const dataKey = (dataIndex || key) as string;

		if (!record.editable) {
			const { getBindValues } = table;

			const { beforeEditSubmit, columns } = getBindValues;

			if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
				// spinning.value = true;
				setSpinning(true);
				const keys: string[] = columns.map(_column => _column.dataIndex).filter(field => !!field) as string[];
				let result: any = true;
				try {
					result = await beforeEditSubmit({
						record: pick(record, keys),
						index,
						key: dataKey as string,
						value
					});
				} catch (e) {
					result = false;
				} finally {
					// spinning.value = false;
					setSpinning(false);
				}
				if (result === false) return;
			}
		}

		set(record, dataKey, value);
		// defaultValueRef.value = value;
		defaultValueRef = value;
		// const record = await table.updateTableData(index, dataKey, value);
		needEmit && table.emit?.("editEnd", { record, index, key: dataKey, value });
		// isEdit.value = false;
		setIsEdit(false);
	}

	async function handleEnter() {
		if (props.column?.editRow) return;

		handleSubmit();
	}

	function handleSubmitClick() {
		handleSubmit();
	}

	function handleCancel() {
		// isEdit.value = false;
		setIsEdit(false);
		// currentValueRef.value = defaultValueRef.value;
		setCurrentValueRef(defaultValueRef);
		const { column, index, record } = props;
		const { key, dataIndex } = column;
		table.emit?.("editCancel", {
			record,
			index,
			key: dataIndex || key,
			value: currentValueRef
		});
	}

	// function onClickOutside() {
	// 	if (props.column?.editable || getRowEditable) return;

	// 	const component = getComponent;

	// 	if (component.includes("Input")) handleCancel();
	// }

	// only ApiSelect or TreeSelect
	function handleOptionsChange(options) {
		const { replaceFields } = getComponentProps;
		const component = getComponent;
		if (component === "ApiTreeSelect") {
			const { title = "title", value = "value", children = "children" } = replaceFields || {};
			let listOptions = treeToList(options, { children });
			listOptions = listOptions.map(item => {
				return {
					label: item[title],
					value: item[value]
				};
			});
			// optionsRef = listOptions;
			setOptionsRef(listOptions);
		} else {
			// optionsRef = options;
			setOptionsRef(options);
		}
	}

	function initCbs(cbs: "submitCbs" | "validCbs" | "cancelCbs", handle) {
		if (props.record) {
			isArray(props.record[cbs]) ? props.record[cbs]?.push(handle) : (props.record[cbs] = [handle]);
		}
	}

	if (props.record) {
		initCbs("submitCbs", handleSubmit);
		initCbs("validCbs", handleSubmitRule);
		initCbs("cancelCbs", handleCancel);

		if (props.column.dataIndex) {
			if (!props.record.editValueRefs) props.record.editValueRefs = {};
			props.record.editValueRefs[props.column.dataIndex as any] = currentValueRef;
		}
		props.record.onCancelEdit = () => {
			isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach(fn => fn());
		};
		props.record.onSubmitEdit = async () => {
			if (isArray(props.record?.submitCbs)) {
				if (!props.record?.onValid?.()) return;
				const submitFns = props.record?.submitCbs || [];
				submitFns.forEach(fn => fn(false, false));
				table.emit?.("editRowEnd");
				return true;
			}
		};
	}

	// const testFunc = () => {
	// 	return {
	// 		isEdit,
	// 		prefixCls,
	// 		handleEdit,
	// 		currentValueRef,
	// 		handleSubmit,
	// 		handleChange,
	// 		handleCancel,
	// 		elRef,
	// 		getComponent,
	// 		getRule,
	// 		onClickOutside,
	// 		ruleMessage,
	// 		getRuleOpen,
	// 		getComponentProps,
	// 		handleOptionsChange,
	// 		getWrapperStyle,
	// 		getWrapperClass,
	// 		getRowEditable,
	// 		getValues,
	// 		handleEnter,
	// 		handleSubmitClick,
	// 		spinning
	// 	};
	// };

	// console.log(testFunc);

	/** Effect */

	useEffect(() => {
		// currentValueRef.value = props.value;
		setCurrentValueRef(props.value);
	}, [props.value]);

	useEffect(() => {
		const { editable } = props.column;
		if (isBoolean(editable) || isBoolean(getRowEditable)) setIsEdit(!!editable || getRowEditable);
	}, [props.column?.editable]);

	/** Render */

	return (
		<div className={prefixCls}>
			<div
				className={`${prefixCls}__normal  ${props.column.ellipsis ? "ellipsis-cell" : ""} `}
				onClick={handleEdit}
				style={{ display: !isEdit ? "block" : "none" }}
			>
				<div className="cell-content" title={props.column.ellipsis ? getValues ?? "" : ""}>
					{props.column.editRender
						? props.column.editRender({
								text: props.value,
								record: props.record as Recordable,
								column: props.column,
								index: props.index!
						  })
						: getValues ?? "\u00A0"}
				</div>
				{!props.column.editRow && <FormOutlined className={`${prefixCls}__normal-icon`} />}
			</div>
			{isEdit && (
				<Spin spinning={spinning}>
					{/* <div className={`${prefixCls}__wrapper`} v-click-outside={this.onClickOutside}> */}
					<div className={`${prefixCls}__wrapper`}>
						<CellComponent
							{...getComponentProps}
							component={getComponent}
							style={getWrapperStyle}
							popoverOpen={getRuleOpen}
							rule={getRule}
							ruleMessage={ruleMessage}
							className={getWrapperClass}
							ref={elRef}
							onChange={handleChange}
							onOptionsChange={handleOptionsChange}
							onPressEnter={handleEnter}
						/>
						{!getRowEditable && (
							<div className={`${prefixCls}__action`}>
								<CheckOutlined className={`${prefixCls}__icon mx-2`} onClick={handleSubmitClick} />
								<CloseOutlined className={`${prefixCls}__icon `} onClick={handleCancel} />
							</div>
						)}
					</div>
				</Spin>
			)}
		</div>
	);
};

export default EditableCell;
